/**
 * Copyright 2007 Jason Horman, Pete Aykroyd
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.functor;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;

/**
 * A org.functor that takes no arguments and returns a <code>boolean</code> value.
 */
public abstract class Predicate extends BooleanFunctor<Predicate> {
	/**
	 * Evaluate this predicate.
	 *
	 * @return the result of this test
	 */
	public abstract boolean test();

	public Predicate and(final Predicate predicate) {
		return new And(this, predicate);
	}

	public Predicate or(final Predicate predicate) {
		return new Or(this, predicate);
	}

	public static Predicate not(final Predicate predicate) {
		return new Not(predicate);
	}

	public Predicate not() {
		return not(this);
	}

	/**
	 * And's a number of {@link UnaryPredicate unary predicates} together. Short
	 * circuits, meaning the first false stops the test.
	 */
	public static class And extends PredicateJunction implements org.functor.And<Predicate>{
		public List<Predicate> predicates;

		public And() {
			this.predicates = new ArrayList();
		}

		public And(Predicate ... predicates) {
			this.predicates = Arrays.asList(predicates);
		}

		public void add(Predicate predicate) {
			predicates.add(predicate);
		}

		public boolean isEmpty() {
			return predicates.isEmpty();
		}

		public List<Predicate> getPredicates() {
			return predicates;
		}

		public boolean test() {
			for (Predicate predicate : predicates) {
				if (!predicate.test())
					return false;
			}

			return true;
		}

		public String toString() {
			StringBuffer str = new StringBuffer();
			for (Iterator<Predicate> iterator = predicates.iterator(); iterator.hasNext();) {
				Predicate predicate = iterator.next();
				str.append(predicate);
				if (iterator.hasNext())
					str.append(" AND ");
			}

			return str.toString();
		}
	}

	/** Or's a number of {@link UnaryPredicate unary predicates} together. */
	public static class Or extends PredicateJunction implements org.functor.Or<Predicate> {
		public List<Predicate> predicates;

		public Or() {
			this.predicates = new ArrayList();
		}

		public Or(Predicate ... predicates) {
			this.predicates = Arrays.asList(predicates);
		}

		public void add(Predicate predicate) {
			predicates.add(predicate);
		}

		public boolean isEmpty() {
			return predicates.isEmpty();
		}

		public boolean test() {
			for (Predicate predicate : predicates) {
				if (predicate.test())
					return true;
			}

			return false;
		}

		public List<Predicate> getPredicates() {
			return predicates;
		}

		public String toString() {
			StringBuffer str = new StringBuffer();
			for (Iterator<Predicate> iterator = predicates.iterator(); iterator.hasNext();) {
				Predicate predicate = iterator.next();
				str.append(predicate);
				if (iterator.hasNext())
					str.append(" OR ");
			}

			return str.toString();
		}
	}

	public static final Predicate TRUE = new Predicate() {
		public boolean test() {
			return true;
		}

		public Predicate or(Predicate predicate) {
			return this;
		}

		public Predicate not() {
			return FALSE;
		}

		public String toString() {
			return "true";
		}
	};

	public static final Predicate FALSE = new Predicate() {
		public boolean test() {
			return false;
		}

		public Predicate and(Predicate predicate) {
			return this;
		}

		public Predicate not() {
			return TRUE;
		}

		public String toString() {
			return "false";
		}
	};

	private static class Not extends Predicate implements org.functor.Not<Predicate> {
		private final Predicate predicate;

		public Not(Predicate predicate) {
			this.predicate = predicate;
		}

		public Predicate getPredicate() {
			return predicate;
		}

		public boolean test() {
			return !predicate.test();
		}

    public String toString() {
			return "~(" + predicate + ")";
		}
	}
}
